using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System;
using System.Collections.Concurrent;
using QTool;
using QTool.Binary;
using UnityEngine;
using System.Threading.Tasks;
#if QSteamworks
using Steamworks;
using QSteamworks;
#endif
namespace QTool.NetFrame.Core
{
 
    /// <summary>
    /// 提供确定送达的通讯 不保证数据的顺序 包含Steam通讯支持
    /// </summary>
    public class QControlProtocol
    {

		/// <summary>
		/// 最大数据大小
		/// </summary>
        const int MaxSize = 65507;

		/// <summary>
		/// 接收消息事件
		/// </summary>

        public event Action<byte[], QNetConnectId> OnMessageReceive;
		/// <summary>
		/// 连接信息列表
		/// </summary>

		public QList<QNetConnectId, ConnectInfo> ConnectList = new QList<QNetConnectId, ConnectInfo>(()=>new ConnectInfo());
        public bool Running
        {
            get
            {
#if QSteamworks
                if (QNetManager.Instance.useSteamNet)
                {
                    if (!SteamManager.Initialized) return false;
                    return QSteam.Id != default;
                }
                else

#endif
                {
                    return udp != null;
                }


            }
        }
        public static IPAddress GetHostAddress()
        {
            foreach (var ad in Dns.GetHostEntry("").AddressList)
            {
                if(ad.AddressFamily== AddressFamily.InterNetwork)
                {
                    return ad;
                }
            }
            return null;
        }
        public QControlProtocol(int port = -1)
        {
#if QSteamworks
            if (!QNetManager.Instance.useSteamNet)
#endif
            {
                udp = port > 0 ? new UdpClient(port) : new UdpClient();
                uint IOC_IN = 0x80000000;
                uint IOC_VENDOR = 0x18000000;
                uint SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12;
                //if (udp.Client.LocalEndPoint!= null){
                //    LocalId = new IPEndPoint(GetHostAddress(), int.Parse(udp.Client.LocalEndPoint.ToString().SplitEndString(":")));
                //};
            }
           // udp.Client.IOControl((int)SIO_UDP_CONNRESET, new byte[] { Convert.ToByte(false) }, null);
        }
        public SecondsAverageList SendSize = new SecondsAverageList();
        public SecondsAverageList ReceiveSize = new SecondsAverageList();
        public SecondsAverageList Delay = new SecondsAverageList();

        protected UdpClient udp;
        /// <summary>
        /// 开始接收消息
        /// </summary>
        /// <param name="receiveCount">接收通道数</param>
        public void StartReceive()
        {

#if QSteamworks
            if (QNetManager.Instance.useSteamNet)
            {
                QSteam.OnReceiveMessage +=(bytes,steamId)=> { ParseResult(bytes, new QNetConnectId { steamId = steamId }); };
            }
            else
#endif
            {
                for (int i = 0; i < 5; i++)
                {
                    ReceiveAsync();
                }
            }

        }
  
        /// <summary>
        /// 接收消息
        /// </summary>
        async void ReceiveAsync()
        {
            if (udp == null) return;

            var result = await Task.Run<UdpReceiveResult>(() =>
            {
                try
                {
                    return udp.ReceiveAsync().Result;
                }
                catch (Exception e)
                {
                    UnityEngine.Debug.LogWarning(e);
                    return default;
                }
            });

            if (udp == null) return;
            ReceiveAsync();
            try
            {
                ParseResult(result.Buffer,new QNetConnectId { ip= result.RemoteEndPoint });
            }
            catch (Exception e)
            {

               UnityEngine.Debug.LogError("解析数据出错：" + e);
            }
        }

#region 解析消息
      
        private void ReceiveMsg(byte[] msg,QNetConnectId id)
        {
            if (msg == null || msg.Length == 0) return;
            try
            {
                OnMessageReceive?.Invoke(msg, id);
            }
            catch (Exception e)
            {
                UnityEngine.Debug.LogError(e);
            }
        }
        private void ParseResult(byte[] buffer, QNetConnectId id)
        {
            if (buffer != null)
            {

                using (QBinaryReader reader = new QBinaryReader(buffer))
                {
                    var type = (MsgType)reader.ReadByte();

                    switch (type)
                    {
                        case MsgType.Normal:
                            {
                                var bytes = reader.ReadBytes();
                                ReceiveMsg(bytes, id);
                            }
                            break;
                        case MsgType.RSendCheck:
                            {
                                var connectInfo = ConnectList[id];
                                lock (connectInfo)
                                {

                                    var msgIndex = reader.ReadUInt16();
                                    byte[] bytes = reader.ReadBytes();
                                    var missIndex = reader.ReadUInt16();
                                    var receiveIndex = reader.ReadUInt16();
                                    if (connectInfo.CheckIndex(msgIndex))
                                    {
                                        ReceiveMsg(bytes, id);
                                    }
                                    else if (connectInfo.MissList.Contains(msgIndex))
                                    {
										Debug.LogWarning("重新获取信息[" + id + "]【" + msgIndex + "】" + "当前信息");
                                        ReceiveMsg(bytes, id);
                                        connectInfo.MissList.Remove(msgIndex);
                                    }
                                    if (connectInfo.MissList.Count > 0)
                                    {
										if (Time.time - connectInfo.lastCheckTime > 0.1f)
										{
											Send((byte[])null, MsgType.RSendCheck, connectInfo.Key);
										}
									}
                                    connectInfo.SendMissMsg(missIndex, receiveIndex, (index, data) =>
                                    {
                                        Send((w) => { w.Write(index); w.Write(data); }, MsgType.RSendInfo, id);
										Debug.LogWarning("重传信息[" + id + "]【" + index + "】");
                                    });
                                }

                            }

                            break;
                        case MsgType.RSendInfo:
                            {
                                var connectInfo = ConnectList[id];
                                lock (connectInfo)
                                {

                                    var msgIndex = reader.ReadUInt16();
                                    byte[] bytes = reader.ReadBytes();

                                    if (connectInfo.MissList.Contains(msgIndex))
                                    {
										Debug.LogWarning("接收重传信息[" + id + "]【" + msgIndex + "】" + connectInfo.MissList.ToOneString(","));
                                        ReceiveMsg(bytes, id);
                                        connectInfo.MissList.Remove(msgIndex);
                                    }
                                }
                            }
                            break;
                        default:
                            {
                                UnityEngine.Debug.LogError("接收消息出错" + type);
                            }
                            break;
                    }

                }
                ReceiveSize.Push(buffer.LongLength);
            }
        }
		/// <summary>
		/// 本地Id
		/// </summary>
        public QNetConnectId LocalId
        {
            get
            {

                if (_localId.IsNull)
                {
#if QSteamworks
                    if (QNetManager.Instance.useSteamNet)
                    {
                        if (QSteam.Id!=default)
                        {
                            _localId = SteamUser.GetSteamID() ;
                        }
                    }
                    else
#endif
                    if (udp != null && udp.Client.LocalEndPoint != null)
                    {
                       // GetHostAddress()
                        _localId = new IPEndPoint(IPAddress.Parse("127.0.0.1"), int.Parse(udp.Client.LocalEndPoint.ToString().SplitEndString(":"))) ;
						Debug.Log(nameof(LocalId) + ":" + _localId);
                    }
                }
                
                return _localId;
            }
        }
        QNetConnectId _localId;
        void Send(Action<QBinaryWriter> writerAction, MsgType udpType, QNetConnectId id)
        {
            if (writerAction==null)
            {
                UnityEngine.Debug.LogError("消息为空 " + udpType);
                return;
            }
            try
            {
                using (QBinaryWriter writer = new QBinaryWriter())
                {
                    writer.Write((byte)udpType);
                    writerAction?.Invoke(writer);
                    var msg = writer.ToArray();
                    if (id.Equals(LocalId))
                    {
                        ParseResult(msg, id);
                        return;
                    }
                    else
                    {
                        SendAsync(msg, msg.Length, id);
                    }
                }
            }
            catch (Exception e)
            {
                throw new Exception("发送" + udpType + "消息失败:" , e);
            }
        }
        void Send(byte[] bytes, MsgType udpType, QNetConnectId id)
        {
            try
            {
                Send((writer) =>
                {
                    switch (udpType)
                    {
                        case MsgType.Normal:
                        case MsgType.RSendInfo:
                            {
                                writer.Write(bytes);
                            }
                            break;
                        case MsgType.RSendCheck:
                            {
                                var connectInfo = ConnectList[id];
                                lock (connectInfo)
                                {
                                    writer.Write(connectInfo.GetSenIndex());
                                    writer.Write(bytes);
                                    writer.Write(connectInfo.Push(bytes));
                                    writer.Write(connectInfo.receiveIndex);
									connectInfo.lastCheckTime = Time.time;
                                }
                            }
                            break;
                        default:
                            throw new Exception("错误的消息类型[" + udpType + "]:" + bytes?.Length);

                    }
                }, udpType, id);
            }
            catch (Exception e)
            {
                throw new Exception("发送" + udpType + "消息失败:" + bytes?.Length, e);
            }
        }
#endregion

        public void Send<KeyType>(byte[] bytes, MsgType type, List<KeyType> ipEnd) where KeyType : IKey<QNetConnectId>
        {
            foreach (var ip in ipEnd)
            {
                Send(bytes, type, ip.Key);
            }
        }
        public void Broadcast(byte[] bytes, int port)
        {
            Send(bytes, MsgType.Normal, new IPEndPoint(IPAddress.Broadcast, port));

        }
        public void Send(byte[] bytes, MsgType type,params QNetConnectId[] ids)
        {
            foreach (var ip in ids)
            {
                Send(bytes, type, ip);
            }

        }
#region 发送消息
        private async void SendAsync(byte[] bytes, int lenghth,QNetConnectId id)
        {
            if (!Running) return;
            if (bytes.Length > MaxSize)
            {
                UnityEngine.Debug.LogError("数据包过大:" + bytes.Length + "/" + MaxSize + "放弃发送");
                return;
            }
            var sendCount = 0;
#if QSteamworks
            if (QNetManager.Instance.useSteamNet)
            {


				sendCount = bytes.Length;
                QSteam.SendMessage(id.steamId, bytes);
            }
            else
#endif
            {
                sendCount = await udp.SendAsync(bytes, lenghth, id.ip);
            }

            SendSize.Push(sendCount);
            if (sendCount <= 0)
            {
                UnityEngine.Debug.LogError("发送失败" + id);
            }
        }

#endregion
        public void Close()
        {
            if (!Running)
            {
                return;
            }
#if QSteamworks
            if (QNetManager.Instance.useSteamNet)
            {
                QSteam.ExitLobby();
            }
            else
#endif
            {
				Debug.Log("UDP关闭" +LocalId);
                udp.Close();
                udp = null;
            }
        }
       
    }
	/// <summary>
	/// 连接信息
	/// </summary>
	public class ConnectInfo : IKey<QNetConnectId>
	{
		public float lastCheckTime = 1;
		public QNetConnectId Key { get; set; }
		private ushort sendIndex = ushort.MaxValue;
		public ushort receiveIndex { private set; get; } = ushort.MaxValue;
		public ushort GetSenIndex()
		{
			sendIndex = Next(sendIndex);
			return sendIndex;
		}
		ushort Next(ushort value)
		{
			value++;
			if (value == 0)
			{
				value = 1;
			}
			return value;
		}
		public bool CheckIndex(ushort curIndex)
		{
			var flag = Less(receiveIndex, curIndex);
			if (flag)
			{
				for (ushort t = Next(receiveIndex); Less(t, curIndex); t = Next(t))
				{
					MissList.AddCheckExist(t);
					UnityEngine.Debug.LogError(receiveIndex + "=>" + curIndex + "丢失信息[" + Key + "][" + t + "] 【" + MissList.ToOneString("|") + "】");
				}
				receiveIndex = curIndex;
			}
			return flag;
		}
		public static bool Less(ushort a, ushort b)
		{
			var offset = a - b;
			var flag = false;
			if (offset < 0)
			{
				flag = true;
			}
			else
			{
				flag = (ushort)offset > ushort.MaxValue / 2;
			}
			return flag;
		}
		public List<ushort> MissList = new List<ushort>();
		QDictionary<ushort, byte[]> historyMsg = new QDictionary<ushort, byte[]>();
		public ushort Push(byte[] bytes)
		{
			historyMsg.Add(sendIndex, bytes);
			var missIndex = MissList.Count > 0 ? MissList.QueuePeek() : (ushort)0;
			return missIndex;
		}
		ushort lastMiss = 0;
		float lastSendMissTime = 0;
		ushort confirmIndex = ushort.MaxValue;
		List<ushort> buffer = new List<ushort>();
		public void SendMissMsg(ushort miss, ushort receive, System.Action<ushort, byte[]> sendAction)
		{
			if (miss == 0)
			{
				historyMsg.RemoveAll((kv) => Less(kv.Key, receive), buffer);
				confirmIndex = receive;
			}
			else if (Less(confirmIndex, receive))
			{
				historyMsg.RemoveAll((kv) => Less(kv.Key, miss),buffer);
				if (historyMsg.ContainsKey(miss))
				{
					if (lastMiss != miss || (Time.time - lastSendMissTime) > 1)
					{

						lastMiss = miss;
						lastSendMissTime = Time.time;
						sendAction.Invoke(miss, historyMsg[miss]);
					}
				}
				else
				{
					Debug.LogError("丢失历史信息【" + Key + "】【" + miss + "】" + ":[" + receive + "][" + confirmIndex + "]");
				}
				confirmIndex = miss;
			}

		}
	}
	public enum MsgType : byte
	{
		/// <summary>
		/// 普通消息 可能会丢失
		/// </summary>
		Normal = 101,
		/// <summary>
		/// 重传检测消息  信息丢失会在下一个消息发送时检测丢失信息 发送重传信息
		/// </summary>
		RSendCheck = 103,
		/// <summary>
		/// 重传信息 发送上一次发送丢失的信息
		/// </summary>
		RSendInfo = 203,
	}
	public struct QNetConnectId
	{
		public IPEndPoint ip;

		public static implicit operator QNetConnectId(IPEndPoint ip)
		{
			return new QNetConnectId { ip = ip };
		}
		public static implicit operator IPEndPoint(QNetConnectId id)
		{
			return id.ip;
		}
		public bool IsNull
		{
			get
			{
#if QSteamworks
				if (QNetManager.Instance.useSteamNet)
				{
					return steamId == default;
				}
				else
#endif
				{
					return ip == null;
				}
			}

		}
		public override string ToString()
		{
#if QSteamworks
			if (QNetManager.Instance.useSteamNet)
			{
				return steamId.ToString();
			}
			else
#endif
			{
				return ip?.ToString();
			}
		}
#if QSteamworks
		public CSteamID steamId;
		public static implicit operator QNetConnectId(CSteamID steamId)
		{
			return new QNetConnectId { steamId = steamId };
		}
		public static implicit operator CSteamID(QNetConnectId id)
		{
			return id.steamId;
		}
#endif
	}
}
